#define _CRT_SECURE_NO_WARNINGS

/* in code we trust */

/********************************************************
 * strong component condensation - Kosaraju/Sharir Algo *
 * by marqueewinq - Troizk, 2011                        *
 ********************************************************/

/*
	Input: graph G(set V, set E)
	Output: graph G*(V*, E*)

	In current solution, G is represented as incident list.
	
	Step 1.
		Topsort graph G.
	Step 2.
		Build graph T(VT, ET), which is transposed for G(V, E).
		In current solution, we'll build T on input.
	Step 3.
		Walk through T in order of descreasing of exit time (calculated
		on Step 1); on each dfs(), all visited vertexes belongs to one
		strong component.
*/

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <iomanip>
#include <vector>
#include <set>
#include <stack>
#include <deque>
#include <string>
#include <algorithm>
#include <cmath>
#include <sstream>
 
using namespace std;

#define _ << " "
#define __ << " " <<
#define eol << "\n"
#define eol_ << "\n" <<

#define prec(_value) fixed << setprecision(_value) <<

#define uint unsigned int
#define oint long long
#define sdouble long double

#define runx(n)     for (int __loop__ = 0; __loop__ < n; __loop__++)
#define forx(i,n)   for (int i = 0; i < n; i++)
#define repx(i,n,s) for (int i = 0; i < n; i += s)

vector <vector <int> > given, trans;

/* topsort */

vector <bool> used;

void dfs1(int point, vector <int>& order)
{
	used[point] = true;

	forx(loop, given[point].size())
		if (!used[given[point][loop]])
		{
			dfs1(given[point][loop], order);
		}

	order.push_back(point);
}

vector <int> topsort(vector <vector <int> >& given)
{
	vector <int> result;
	used.resize(given.size(), false);

	forx(loop, given.size())
	{
		if (!used[loop])
			dfs1(loop, result);
	}

	return result;
}

/* ----- topsort ----- */

vector <int> component;
void dfs2(int point)
{
	used[point] = true;
	component.push_back(point);

	forx(loop, trans[point].size())
		if (!used[trans[point][loop]])
		{
			dfs2(trans[point][loop]);
		}
}

int main(void)
{
#ifdef CUCUMBER
	freopen("input.txt","r",stdin);
	freopen("output.txt","w",stdout);
#endif

	int n, m;
	cin >> n >> m;
	given.resize(n, vector <int> (0));
	trans = given;

	// general input: graph G
	forx(loop, m)
	{
		int from, to;
		cin >> from >> to;
		from--; to--;
		given[from].push_back(to);

		// executing Step 2.
		trans[to].push_back(from);
	}

	// Step 1.
	vector <int> sorted = topsort(given);

	// Step 3.
	used.assign(n, false);
	vector <int> answer(n, 0);
	int components_count = 0;
	for (int loop = sorted.size() - 1; loop >= 0; loop--)
	{
		if (!used[sorted[loop]])
		{
			dfs2(sorted[loop]);

			forx(loop2, component.size())
			{
				answer[component[loop2]] = components_count + 1;
			}
			components_count++;
			component.clear();
		}
	}

	/* general output; i-th number x defines, 
		that i-th vertex belongs to x-th component */
	cout << components_count eol;
	forx(loop, n)
		cout << answer[loop] _;
	cout eol;

    return 0;
}
